package main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

public class Tokenizer {
	/**
	 * List of tokens.
	 */
	private ArrayList<String> tokens = new ArrayList<String>();
	/**
	 * List of symbols generated from the tokens.
	 */
	private ArrayList<Symbol> symbols = new ArrayList<Symbol>();

	/**
	 * String with program name in it.
	 */
	private String program;

	/**
	 * Tokenizer Constructor. Condenses file to one string opject to be tokenized.
	 * @param file Input file to be tokenized
	 */
	public Tokenizer(String file) {
		try {
			FileReader input = new FileReader(file);
			BufferedReader read = new BufferedReader(input);

			//read in entire program
			program = "";
			String temp = read.readLine();
			while (temp != null) {
				program = program.concat(temp);
				temp = read.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//TODO add comments to everything

	/**
	 * Separates input file into meaningful tokens.
	 */
	public void tokenize() {

		StringTokenizer tokenizer = new StringTokenizer(program);
		while (tokenizer.hasMoreTokens()) {
			String tok = "";
			//get next token in the file
			tok = tokenizer.nextToken();
			
			if(ReservedSymbols.containsIdentifier(tok) || ReservedSymbols.containsOperation(tok)) {
				//if something is already in the newToken add it
				String newToken = "";
				int pos = 0;
				while (tok.length() > pos) {
					//
					if (Character.isLetter(tok.charAt(pos)) || Character.isDigit(tok.charAt(pos))) {
						newToken = newToken.concat(tok.substring(pos, pos + 1));
						pos++;
					} else {
						if (pos > 0 && !newToken.equals("")) {
							tokens.add(newToken);
							newToken = "";
						}
						if (pos+1 < tok.length() && !(ReservedSymbols.containsIdentifier(tok.substring(pos, pos+1))) && tok.charAt(pos) != ';') {
							if (tok.charAt(pos+1 ) == '=') {
								tokens.add(tok.substring(pos, pos + 2));
								pos += 2;
							} else {
								tokens.add(tok.substring(pos, pos + 1));
								pos++;
							}
						} else {
							if (tok.charAt(pos) == ':') {
								System.out.println("Malformed input : assignment is done with \":=\" in this language"); 
								System.exit(0);
							}
							tokens.add(tok.substring(pos, pos + 1));
							pos++;
						}
					}
				} // while length greater than pos
				if (!newToken.equals("")) {
					tokens.add(newToken);
				}
			} // if contains identifier or operation
			else {
				if (!Character.isLetter(tok.charAt(0)) && !Character.isDigit(tok.charAt(0)) && !ReservedSymbols.containsIdentifier(tok)) {
					System.out.println("Malformed input : Assignment is done with \":=\" in this language"); 
					System.exit(0);
				}

				tokens.add(tok);
			}
		}
	}

	/**
	 * Return all tokens.
	 * @return List of tokens
	 */
	public ArrayList<String> getTokens() {
		return this.tokens;
	}

	/**
	 * Sets tokens.
	 * @param token
	 */
	public void setTokens(ArrayList<String> token) {
		this.tokens = token;
	}

	/**
	 * Takes tokens and determines what type they are. Creates a symbol table with all tokens.
	 * @return Symbol table that is produced.
	 */
	public ArrayList<Symbol> getSymbols() {
		int pos = 0;
		String symbol = "";
		while (tokens.size() > pos) {
			symbol = "";
			symbol = tokens.get(pos);
			// is conditional while, if ect...
			if (ReservedSymbols.containsConditional(symbol)) {
				if (symbol.equals(ReservedSymbols.S_WHILE))
					symbols.add(new Symbol(Symbol.I_WHILE));
				else if (symbol.equals(ReservedSymbols.S_DO))
					symbols.add(new Symbol(Symbol.I_DO));
				else if (symbol.equals(ReservedSymbols.S_IF))
					symbols.add(new Symbol(Symbol.I_IF));
				else if (symbol.equals(ReservedSymbols.S_ELSE))
					symbols.add(new Symbol(Symbol.I_ELSE));
				else {
					System.out.println("Error undefined symbol");
					System.exit(0);
				}
			}
			//is identifier (, ), {, }
			else if (ReservedSymbols.containsIdentifier(symbol)) {
				if (symbol.equals(ReservedSymbols.S_BLOCK_START_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_BLOCK_START_IDENTIFIER));
				else if (symbol.equals(ReservedSymbols.S_BLOCK_END_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_BLOCK_END_IDENTIFIER));
				else if (symbol.equals(ReservedSymbols.S_CONDITIONAL_START_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_CONDITIONAL_START_IDENTIFIER));
				else if (symbol.equals(ReservedSymbols.S_CONDITIONAL_END_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_CONDITIONAL_END_IDENTIFIER));
				else {
					System.out.println("Error undefined symbol");
					System.exit(0);
				}
			}
			// is input output
			else if (ReservedSymbols.containsIO(symbol)) {
				if (symbol.equals(ReservedSymbols.S_INPUT))
					symbols.add(new Symbol(Symbol.I_INPUT));
				else if (symbol.equals(ReservedSymbols.S_OUTPUT))
					symbols.add(new Symbol(Symbol.I_OUTPUT));
				else {
					System.out.println("Error undefined symbol");
					System.exit(0);
				}

			}
			// is operation +, -, /, *, ;, :=, >, < ect...
			else if (ReservedSymbols.containsOperation(symbol)){
				if (symbol.equals(ReservedSymbols.S_PLUS))
					symbols.add(new Symbol(Symbol.I_PLUS));
				else if (symbol.equals(ReservedSymbols.S_MINUS))
					//TODO negative or minus
					if (ReservedSymbols.containsIdentifier(tokens.get(pos-1)) || ReservedSymbols.containsOperation(tokens.get(pos-1))) {
						symbols.add(new Symbol(Symbol.I_NEGATIVE));
					} else {
						symbols.add(new Symbol(Symbol.I_MINUS));
					}
				else if (symbol.equals(ReservedSymbols.S_MULTIPLY))
					symbols.add(new Symbol(Symbol.I_MULTIPLY));
				else if (symbol.equals(ReservedSymbols.S_DIVIDE))
					symbols.add(new Symbol(Symbol.I_DIVIDE));
				else if (symbol.equals(ReservedSymbols.S_GTSYMBOL))
					symbols.add(new Symbol(Symbol.I_GTSYMBOL));
				else if (symbol.equals(ReservedSymbols.S_GTESYMBOL))
					symbols.add(new Symbol(Symbol.I_GTESYMBOL));
				else if (symbol.equals(ReservedSymbols.S_LTSYMBOL))
					symbols.add(new Symbol(Symbol.I_LTSYMBOL));
				else if (symbol.equals(ReservedSymbols.S_LTESYMBOL))
					symbols.add(new Symbol(Symbol.I_LTESYMBOL));
				else if (symbol.equals(ReservedSymbols.S_EOL_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_EOL_IDENTIFIER));
				else if (symbol.equals(ReservedSymbols.S_EQUALS_IDENTIFIER))
					symbols.add(new Symbol(Symbol.I_EQUALS_IDENTIFIER));
				else if (symbol.equals(ReservedSymbols.S_EQUIVSYMBOL))
					symbols.add(new Symbol(Symbol.I_EQUIVSYMBOL));
				else if (symbol.equals(ReservedSymbols.S_NOTEQL))
					symbols.add(new Symbol(Symbol.I_NOTEQL));
				else {
					System.out.println("Error undefined symbol");
					System.exit(0);
				}
			}
			// else is number or identifier
			else {
				if (Character.isDigit(symbol.charAt(0))) {
					int i = 0;
					try {
						i = Integer.parseInt(symbol);
					} catch (NumberFormatException nfe) {
						symbols.add(new Symbol(Symbol.I_NUMBER, true, 0));
					}
					symbols.add(new Symbol(Symbol.I_NUMBER, false, i));
				} else {
					symbols.add(new Symbol(Symbol.I_IDENT, symbol));
				}
			}
			pos++;
		}// end while size > position
		return symbols;
	}

	/**
	 * For testing prints tokens to the screen
	 */
	public void printTokens() {
		int count = 0;
		Machine.out.println("List of relavent tokens\n");
		while (tokens.size() > count) {
			Machine.out.println(tokens.get(count));
			count++;
		}
		System.out.println("\n");
	}

	/**
	 * For testing prints symbols to the screen
	 */
	public void printSymbols() {
		int count = 0;
		Machine.out.println("Symbol type \tID \tOverflow \tNum\n");
		while (symbols.size() > count) {
			Machine.out.println(count + ": \t" + symbols.get(count).symtype + " \t" + symbols.get(count).id + " \t" + symbols.get(count).overflow + " \t\t" +symbols.get(count).num);
			count++;
		}
		System.out.println("\n");
	}
}
